import {
  Component,
  Input,
  Renderer2,
  QueryList,
  EventEmitter,
  ElementRef,
  TemplateRef,
  ChangeDetectionStrategy,
  ViewEncapsulation,
  ContentChildren,
  Output,
  ViewChild,
  ChangeDetectorRef,
  SimpleChanges,
  OnChanges,
  AfterContentInit,
  AfterViewInit,
  OnDestroy,
} from '@angular/core';
import { Observable, Subscription, fromEvent } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { InputBoolean, TypeUtils, LvSizeLD, doFnResult } from '../core/api';
import type { DropdownDirective } from '../dropdown/dropdown.directive';
import { TabComponent } from './tab.component';

@Component({
  selector: 'lv-tabs, [lv-tabs]',
  exportAs: 'lvTabs',
  templateUrl: './tabs.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  host: {
    '[class.lv-tabs]': 'true',
    '[class.lv-tabs-card]': 'lvType === "card"',
    '[class.lv-tabs-step]': 'lvType === "step"',
    '[class.lv-tabs-panel]': 'lvType === "panel"',
    '[class.lv-tabs-underline]': 'lvType === "underline"',
  },
})
export class TabsComponent implements OnChanges, AfterContentInit, AfterViewInit, OnDestroy {
  /** @deprecated: 该属性废弃 */
  @Input() lvSize: LvSizeLD = 'default';

  @Input() lvActiveIndex: number | string;

  @Input() @InputBoolean() lvClosable: boolean = false;

  @Output() readonly lvTabRemove = new EventEmitter<any>();

  @Input() lvTabBarExtraContent: TemplateRef<void> | null = null;

  @Input() lvTabDropdownContent: TemplateRef<void> | null = null;

  @Output() readonly lvActiveIndexChange = new EventEmitter<any>();

  @ContentChildren(TabComponent) TabItems: QueryList<TabComponent>;

  @ViewChild('tabList', { static: false }) tabList: ElementRef<any>;

  @ViewChild('dropdown', { static: false }) dropdown: DropdownDirective;

  @ViewChild('tabWrapper', { static: false }) tabWrapper: ElementRef<any>;
  @ViewChild('extraContent', { static: false }) extraContent: ElementRef<any>;
  @ViewChild('tabContainer', { static: false }) tabContainer: ElementRef<any>;
  @ViewChild('dropdownContent', { static: false }) dropdownContent: TemplateRef<any>;
  @ViewChild('popover') popover;

  @Input() lvType: 'default' | 'card' | 'step' | 'panel' | 'underline' = 'default';
  @Input() lvAfterChange: (
    originTab,
    activeTab,
  ) => (boolean | void | object) | Promise<boolean | void | object> | Observable<boolean | void | object>;

  @Input() lvBeforeChange: (
    originTab,
    activeTab,
  ) => (boolean | void | object) | Promise<boolean | void | object> | Observable<boolean | void | object>;

  public activeIndex;
  public tabListWidth;
  public isScroll = false;
  public dropdownMenu = [];
  public typeUtils = TypeUtils;
  public dropdownVisible = false;
  public popoverVisible = false;
  public tabContainerWidth: number;
  public scrollDistance: number = 0;

  activeItem = {
    width: 0,
    left: 0,
  };

  private clickListener;
  private windowListener;
  private isFirstInit = true; // 是否是第一次设置indicator的left、width
  private wheel$: Subscription;
  private activeChanging = false;
  private dropdownMenuIds: string[] = [];
  private tabsListIO: IntersectionObserver;

  constructor(
    private renderer: Renderer2,
    private cdr: ChangeDetectorRef,
  ) {}

  dropdownShow(): void {
    this.dropdownVisible = true;
  }

  dropdownHide(): void {
    this.dropdownVisible = false;
  }

  popoverShow = (): any =>
    new Promise((resolve) => {
      this.popoverVisible = true;
      resolve(true);
    });

  popoverClose = (): any =>
    new Promise((resolve) => {
      this.popoverVisible = false;
      resolve(true);
    });

  getMaxScrollDistance(): number {
    return this.tabListWidth - this.tabContainerWidth || 0;
  }

  hasChildNodes(div: HTMLDivElement): boolean {
    return !!(div?.childElementCount || div?.innerText);
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.lvActiveIndex) {
      this.activeChange(this.lvActiveIndex);
    }
  }

  tabClick(index, disabled): void {
    this.scrollHandle(index);
    if (!disabled && index !== this.activeIndex) {
      if (!this.activeChanging) {
        this.tabChange(index, true);
      }
    }
  }

  ngOnDestroy(): void {
    this.windowListener();
    this.clickListener();

    // 销毁时终止所有目标元素可见性变化的观察
    this.tabsListIO?.disconnect();
    this.wheel$.unsubscribe();
  }

  removeTab(index, disabled): void {
    if (!disabled) {
      const tabsArray = this.TabItems.toArray();
      this.lvTabRemove.emit(tabsArray[index]);
      const node = this.tabList?.nativeElement?.querySelectorAll('.lv-tab-item')[index];
      this.scrollDistance = this.clampScrollDistance(this.scrollDistance + node.offsetWidth);
      this.setTransform(this.scrollDistance);
      return;
    }
  }

  getExtraContentWidth(): number | undefined {
    if (this.lvTabBarExtraContent !== null) {
      return this.extraContent.nativeElement.clientWidth;
    }
    return undefined;
  }

  dropdownClick(data): void {
    const index = data.item.id;
    this.scrollHandle(index);
    if (index !== this.activeIndex) {
      if (!this.activeChanging) {
        this.tabChange(index, true);
      }
    }
    this.popover?.closeTooltip();
  }

  /**
   * When tab input property changes, the view detect immediately
   */
  tabOnChangeSubscribe(): void {
    this.TabItems.forEach((item): void => {
      item.change$.pipe(takeUntil(item.destroy$)).subscribe((): void => {
        this.cdr.detectChanges();
      });
    });
  }

  scrollHandle(index): void {
    const node = this.tabList?.nativeElement?.querySelectorAll('.lv-tab-item')[index];
    if (!node) {
      return;
    }
    const nodeViewStart = node.offsetLeft + this.scrollDistance; // 元素左边界离容器左边距离
    const nodeViewEnd = node.offsetLeft + node.offsetWidth + this.scrollDistance; // 元素右边界离容器左边距离
    // 选中项在视口内
    if (nodeViewStart >= 0 && nodeViewEnd <= this.tabContainerWidth) {
      return;
    }
    // 选中项在视口左侧
    if (nodeViewStart < 0) {
      const x = nodeViewStart;
      this.scrollDistance = this.clampScrollDistance(this.scrollDistance - x);
    } else if (nodeViewEnd > this.tabContainerWidth) {
      // 选中项在视口右侧
      const x = nodeViewEnd - this.tabContainerWidth;
      this.scrollDistance = this.clampScrollDistance(this.scrollDistance - x);
    }

    this.setTransform(this.scrollDistance);
    this.cdr.detectChanges();
  }

  handleDropdownMenuIdChange(): void {
    this.dropdownMenu = [];
    this.TabItems.forEach((item: TabComponent, index: number): void => {
      if (this.dropdownMenuIds.find((child): boolean => child === String(item?.tabId))) {
        this.dropdownMenu.push({
          label: item.lvDropdownLabel || item.lvTitle,
          id: index,
          lvId: item.lvId,
          disabled: item.lvDisabled,
        });
      }
    });
  }

  emitActiveIndex(tabsArray, index, emit = false): void {
    const timeout = setTimeout((): void => {
      // 待实现: 修复外部动态更新`lvActiveIndex`，`lvActiveIndexChange`被触发临时方案
      if (tabsArray[index]?.lvId) {
        if (emit) {
          this.lvActiveIndexChange.emit(tabsArray[index].lvId);
        }
      } else {
        if (emit) {
          this.lvActiveIndexChange.emit(index);
        }
      }
      this.activeChanging = false;
      clearTimeout(timeout);
    }, 0);
  }

  setActivatedStyle(): void {
    const node = this.tabList?.nativeElement?.querySelector('.lv-tab-item-active');
    const flag = !node || this.lvType === 'step' || this.isFirstInit || !this.TabItems?.length;
    if (flag) {
      this.isFirstInit = false;
      this.activeItem = {
        width: 0,
        left: 0,
      };
      return;
    }

    this.activeItem = {
      width: node.offsetWidth,
      left: node.offsetLeft,
    };

    this.cdr.markForCheck();
  }

  ngAfterViewInit(): void {
    // 待实现: layout导致tabs需要延迟计算，需要要移除
    Promise.resolve().then((): void => {
      this.checkTabs();
      this.tabItemsIntersectionObserver();
    });

    this.setActivatedStyle();
    this.windowListener = this.renderer.listen(window, 'resize', (): void => {
      this.checkTabs();
      this.tabItemsIntersectionObserver();
    });
    this.clickListener = this.renderer.listen(window, 'click', (): void => {
      this.checkTabs();
    });
    this.onWheel();
  }

  ngAfterContentInit(): void {
    this.activeChange(this.lvActiveIndex);

    this.tabOnChangeSubscribe();

    // When removing tab causes TabItems to change, refresh the view manually
    this.TabItems.changes.subscribe((): void => {
      setTimeout((): void => {
        this.activeIndex = null; // issue: #77
        this.activeChange(this.lvActiveIndex);
        this.cdr.detectChanges();
        this.checkTabs();
        this.tabItemsIntersectionObserver();
      }, 0);

      this.tabOnChangeSubscribe();
    });
  }

  getPanelLineStyle(index: number): number {
    const node = this.tabList?.nativeElement?.querySelectorAll('.lv-tab-item')?.[index];

    if (!node) {
      return 0;
    }

    let nodeLeft = 0;

    switch (true) {
      case node.offsetLeft < this.activeItem.left:
        nodeLeft = this.activeItem.left - node.offsetLeft;
        break;
      case node.offsetLeft > this.activeItem.left:
        nodeLeft = -(node.offsetLeft - this.activeItem.left) - this.activeItem.width;
        break;

      default:
        break;
    }

    return nodeLeft;
  }

  // 检测tabList中的tabItem与tabList的父容器相交情况。
  tabItemsIntersectionObserver(): void {
    // 清除已有监听
    this.tabsListIO?.disconnect();

    // 当tabListWidth的长度大于外部容器的长度的时候,需要使用IntersectionObserver对单个的tabItem显示监听
    if (this.tabListWidth > this.tabContainerWidth) {
      this.tabsListIO = new IntersectionObserver((entries): void => this.setDropdownMenuIds(entries), {
        root: this.tabContainer.nativeElement,
        threshold: 0.8,
      });
      const allTabItems = this.tabContainer.nativeElement.querySelectorAll('.lv-tab-item');
      allTabItems.forEach((item): void => {
        this.tabsListIO.observe(item);
      });
    }
  }

  setDropdownMenuIds(entries: IntersectionObserverEntry[]): void {
    entries.forEach((entry: IntersectionObserverEntry): void => {
      // 获取tabId的值来控制需要展示在dropdown中的tabItem
      const _targetId = (entry.target.attributes as any).tabid.value;
      const targetIndex = this.dropdownMenuIds.indexOf(_targetId);

      // 不处在当前显示区域
      if (!entry.isIntersecting) {
        if (targetIndex === -1) {
          this.dropdownMenuIds.push(_targetId);
        }
      } else {
        if (targetIndex > -1) {
          const newDropdownMenuIds = [...this.dropdownMenuIds];
          newDropdownMenuIds.splice(targetIndex, 1);
          this.dropdownMenuIds = newDropdownMenuIds;
        }
      }
    });
    this.handleDropdownMenuIdChange();
  }

  checkTabs(): void {
    const dropdownIconWidth = 33;
    const tabExtraContentWidth = this.getExtraContentWidth();
    if (tabExtraContentWidth !== undefined) {
      this.renderer.setStyle(this.tabWrapper.nativeElement, 'width', `calc(100% - ${tabExtraContentWidth + 16}px)`);
    }

    this.tabContainerWidth = this.tabContainer.nativeElement.clientWidth;
    this.tabListWidth = this.tabList.nativeElement.clientWidth;
    // 被隐藏需要在dropdown中展示的项
    this.isScroll = this.tabListWidth > this.tabContainerWidth;

    // 视图变更后，下一个生命周期重新计算出滚动容器的实际宽度，并渲染视图
    setTimeout((): void => {
      const selectWidth = this.isScroll ? dropdownIconWidth : 0;
      this.renderer.setStyle(this.tabContainer.nativeElement, 'width', `calc(100% - ${selectWidth}px)`);
      this.tabContainerWidth = this.tabContainer.nativeElement.clientWidth;
      // 点击事件判断不再滑动
      this.cdr.markForCheck();
    }, 0);
  }

  tabChange(index, emit = false): void {
    this.activeChanging = true; // 切换标志，避免重复点击切换导致死循环
    const tabsArray = this.TabItems.toArray();
    const originTab = tabsArray[this.activeIndex];
    const activeTab = tabsArray[index];
    // tslint:disable-next-line
    originTab && (originTab.index = this.activeIndex);
    // tslint:disable-next-line
    activeTab && (activeTab.index = index);

    const fn = (): void => {
      this.emitActiveIndex(tabsArray, index, emit);
      if (this.activeIndex !== undefined && this.lvAfterChange) {
        this.lvAfterChange(originTab, activeTab);
      }

      this.activeIndex = index;

      this.cdr.detectChanges();

      this.setActivatedStyle();
    };

    if (!this.lvBeforeChange || this.activeIndex === undefined) {
      fn();
    } else {
      const call = this.lvBeforeChange(originTab, activeTab);
      doFnResult(call, (result): void => {
        this.cdr.detectChanges();

        if (result) {
          fn();
        } else {
          this.emitActiveIndex(tabsArray, this.activeIndex, emit);
          this.cdr.markForCheck();
        }
      });
    }
  }

  activeChange(index): void {
    if (this.TabItems && this.TabItems.length > 0) {
      let activeIndex;

      if (this.typeUtils.isNotNil(this.lvActiveIndex)) {
        const tab = this.TabItems.toArray().find((item): boolean => item.lvId === index);
        if (tab === undefined) {
          if (typeof index === 'number' && index >= 0 && index < this.TabItems.length) {
            activeIndex = index;
          } else if (typeof index === 'number') {
            activeIndex = this.activeIndex < this.TabItems.length ? this.activeIndex : 0;
          } else if (typeof index !== 'number') {
            /**
             * 动态添加tab且activeIndex同时变更，id使用string类型。
             * activeIndex变更，ngOnchange执行，此时tabItems还没有新增项，找不到对应的tab()，无需继续对activeIndex做逻辑处理，直接返回。
             * tabItems发生变更，新增的tabItem加载完成后，使用正确的activeIndex值与id进行对比，查找到正确的tab索引并切换
             */
            return;
          } else {
            return;
          }
        } else {
          activeIndex = this.TabItems.toArray().findIndex((item): boolean => item.lvId === index);
        }
      } else {
        activeIndex = 0;
      }

      if (this.activeIndex !== activeIndex) {
        this.tabChange(activeIndex);
      }
    } else {
      this.setActivatedStyle(); // 当删除到只剩下最后一个的时候需要移除指示器的显示
    }
  }

  private clampScrollDistance(scrollDistance: number): number {
    return Math.min(0, Math.max(-1 * this.getMaxScrollDistance(), scrollDistance));
  }

  private setTransform(scrollDistance: number): void {
    this.renderer.setStyle(this.tabList.nativeElement, 'transform', `translate3d(${scrollDistance}px, 0, 0)`);
  }

  private onWheel(): void {
    this.wheel$ = fromEvent<WheelEvent>(this.tabList.nativeElement, 'wheel').subscribe((e): void => {
      // 滚动的时候 关闭dropdown
      if (this.dropdownVisible) {
        this.dropdown?.dropdown?.closeDropDown();
      }
      if (this.getMaxScrollDistance() <= 0) {
        return;
      }
      e.preventDefault();
      const { deltaX, deltaY } = e;
      let mixed: number;
      const absX = Math.abs(deltaX);
      const absY = Math.abs(deltaY);
      if (absX >= absY) {
        mixed = deltaX;
      } else {
        mixed = deltaY;
      }
      this.scrollDistance = this.clampScrollDistance(this.scrollDistance - mixed);
      this.setTransform(this.scrollDistance);
      this.cdr.detectChanges();
    });
  }
}
